package com.ufo.crypto.v1;

import org.apache.commons.codec.binary.Base64;

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static java.util.regex.Pattern.compile;

/**
 * 融合Ras Aes SM4_ECB SM4_CBC算法 加解密工具类
 *
 * @author Sanders 2022-03-18
 * @version 3.1
 */
public class EncryptAndDecryptTool {
    private static int AES_SM4_KEY_LENGTH = 128;
    private static EncryptAndDecryptTool tool;
    private static String algName;
    private static Cipher cipher;
    private static final Map<String, Object> cacheModule = new HashMap<>();
    // RSA最大加密明文大小
    private static final int MAX_ENCRYPT_BLOCK = 117;
    // RSA最大解密密文大小
    private static final int MAX_DECRYPT_BLOCK = 128;

    private static final int SM4_ENCRYPT = 1;

    private static final int SM4_DECRYPT = 0;

    public static final int[] FK = {0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc};

    public static final int[] CK = {
            0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,
            0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,
            0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,
            0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,
            0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,
            0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,
            0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,
            0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279};

    public static final byte[] SboxTable = {(byte) 0xd6, (byte) 0x90, (byte) 0xe9, (byte) 0xfe,
            (byte) 0xcc, (byte) 0xe1, 0x3d, (byte) 0xb7, 0x16, (byte) 0xb6,
            0x14, (byte) 0xc2, 0x28, (byte) 0xfb, 0x2c, 0x05, 0x2b, 0x67,
            (byte) 0x9a, 0x76, 0x2a, (byte) 0xbe, 0x04, (byte) 0xc3,
            (byte) 0xaa, 0x44, 0x13, 0x26, 0x49, (byte) 0x86, 0x06,
            (byte) 0x99, (byte) 0x9c, 0x42, 0x50, (byte) 0xf4, (byte) 0x91,
            (byte) 0xef, (byte) 0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43,
            (byte) 0xed, (byte) 0xcf, (byte) 0xac, 0x62, (byte) 0xe4,
            (byte) 0xb3, 0x1c, (byte) 0xa9, (byte) 0xc9, 0x08, (byte) 0xe8,
            (byte) 0x95, (byte) 0x80, (byte) 0xdf, (byte) 0x94, (byte) 0xfa,
            0x75, (byte) 0x8f, 0x3f, (byte) 0xa6, 0x47, 0x07, (byte) 0xa7,
            (byte) 0xfc, (byte) 0xf3, 0x73, 0x17, (byte) 0xba, (byte) 0x83,
            0x59, 0x3c, 0x19, (byte) 0xe6, (byte) 0x85, 0x4f, (byte) 0xa8,
            0x68, 0x6b, (byte) 0x81, (byte) 0xb2, 0x71, 0x64, (byte) 0xda,
            (byte) 0x8b, (byte) 0xf8, (byte) 0xeb, 0x0f, 0x4b, 0x70, 0x56,
            (byte) 0x9d, 0x35, 0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, (byte) 0xd1,
            (byte) 0xa2, 0x25, 0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, (byte) 0x87,
            (byte) 0xd4, 0x00, 0x46, 0x57, (byte) 0x9f, (byte) 0xd3, 0x27,
            0x52, 0x4c, 0x36, 0x02, (byte) 0xe7, (byte) 0xa0, (byte) 0xc4,
            (byte) 0xc8, (byte) 0x9e, (byte) 0xea, (byte) 0xbf, (byte) 0x8a,
            (byte) 0xd2, 0x40, (byte) 0xc7, 0x38, (byte) 0xb5, (byte) 0xa3,
            (byte) 0xf7, (byte) 0xf2, (byte) 0xce, (byte) 0xf9, 0x61, 0x15,
            (byte) 0xa1, (byte) 0xe0, (byte) 0xae, 0x5d, (byte) 0xa4,
            (byte) 0x9b, 0x34, 0x1a, 0x55, (byte) 0xad, (byte) 0x93, 0x32,
            0x30, (byte) 0xf5, (byte) 0x8c, (byte) 0xb1, (byte) 0xe3, 0x1d,
            (byte) 0xf6, (byte) 0xe2, 0x2e, (byte) 0x82, 0x66, (byte) 0xca,
            0x60, (byte) 0xc0, 0x29, 0x23, (byte) 0xab, 0x0d, 0x53, 0x4e, 0x6f,
            (byte) 0xd5, (byte) 0xdb, 0x37, 0x45, (byte) 0xde, (byte) 0xfd,
            (byte) 0x8e, 0x2f, 0x03, (byte) 0xff, 0x6a, 0x72, 0x6d, 0x6c, 0x5b,
            0x51, (byte) 0x8d, 0x1b, (byte) 0xaf, (byte) 0x92, (byte) 0xbb,
            (byte) 0xdd, (byte) 0xbc, 0x7f, 0x11, (byte) 0xd9, 0x5c, 0x41,
            0x1f, 0x10, 0x5a, (byte) 0xd8, 0x0a, (byte) 0xc1, 0x31,
            (byte) 0x88, (byte) 0xa5, (byte) 0xcd, 0x7b, (byte) 0xbd, 0x2d,
            0x74, (byte) 0xd0, 0x12, (byte) 0xb8, (byte) 0xe5, (byte) 0xb4,
            (byte) 0xb0, (byte) 0x89, 0x69, (byte) 0x97, 0x4a, 0x0c,
            (byte) 0x96, 0x77, 0x7e, 0x65, (byte) 0xb9, (byte) 0xf1, 0x09,
            (byte) 0xc5, 0x6e, (byte) 0xc6, (byte) 0x84, 0x18, (byte) 0xf0,
            0x7d, (byte) 0xec, 0x3a, (byte) 0xdc, 0x4d, 0x20, 0x79,
            (byte) 0xee, 0x5f, 0x3e, (byte) 0xd7, (byte) 0xcb, 0x39, 0x48};

    /**
     * 1、初始化哦工具类 (单例 切换algorithmName参数后会重新加载实例)
     * 2、初始化Cipher基类
     * 3、生成对应类型Key值信息
     *
     * @return EncryptAndDecryptTool对象
     */
    public static EncryptAndDecryptTool getInstance(ALGORITHM_TYPE algorithm_type) {
        String algorithmName = algorithm_type.toString().replaceAll("_", "/");
        if (tool == null || !algorithmName.equals(algName)) {
            try {
                algName = algorithmName;
                tool = new EncryptAndDecryptTool();
                if (!algName.toLowerCase().contains("sm4")) {
                    cipher = Cipher.getInstance(algorithmName);
                    AES_SM4_KEY_LENGTH = 96;
                }
                initKey();
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException("未匹配道德算法类型" + e.getMessage());
            } catch (NoSuchPaddingException e) {
                throw new RuntimeException("错误的扩容" + e.getMessage());
            }
        }
        return tool;
    }

    /**
     * 生成密钥 默认AES方式
     */
    private static void initKey() {
        try {
            if (algName.toLowerCase().contains("rsa")) {
                initRsaKey();
            } else {
                initAesKey();
            }
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("指定的算法类型有误" + e.getMessage());
        }
    }

    private static void initAesKey() throws NoSuchAlgorithmException {
        if (cacheModule.get("aesKey") == null) {
            // AES密钥
            KeyGenerator keyGen = KeyGenerator.getInstance("AES");
            keyGen.init(AES_SM4_KEY_LENGTH);
            // 随机生成IV
            SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG");
//            byte[] ivByte = new byte[cipher.getBlockSize()];
            byte[] ivByte = new byte[AES_SM4_KEY_LENGTH];
            randomSecureRandom.nextBytes(ivByte);
            // 生成的Key
            SecretKey key = keyGen.generateKey();
            IvParameterSpec iv = new IvParameterSpec(ivByte);
            String ivStr = Base64.encodeBase64String(iv.getIV());
            String keyStr = Base64.encodeBase64String(keyGen.generateKey().getEncoded());
            cacheModule.put("aesKey", key);
            cacheModule.put("aesKeyStr", keyStr);
            cacheModule.put("aesIv", iv);
            cacheModule.put("aesIvStr", ivStr);
        }
    }

    private static void initRsaKey() throws NoSuchAlgorithmException {
        KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA");
        keyGenerator.initialize(1024, new SecureRandom());
        KeyPair keyPair = keyGenerator.generateKeyPair();
        if (cacheModule.get("rsaPublicKey") == null || cacheModule.get("rsaPrivateKey") == null) {
            cacheModule.put("rsaPublicKey", keyPair.getPublic());
            cacheModule.put("rsaPrivateKey", keyPair.getPrivate());
        }
    }

    /**
     * @return 获取生成的Key信息
     */
    public Map<String, Object> getKeysInfo() {
        return cacheModule;
    }

    /**
     * 加密
     *
     * @param data 加密的数据
     * @return 对应类型的加密String
     */
    public String encrypt(String data) {
        if (algName.toLowerCase().contains("rsa")) {
            return rsaEncrypt(data);
        } else if (algName.toLowerCase().contains("sm4")) {
            return sm4Encrypt(data);
        } else {
            return aesEncrypt(data);
        }
    }

    /**
     * 解密
     *
     * @param data 解密的数据
     * @return 返回原文
     */
    public String decrypt(String data) {
        if (algName.toLowerCase().contains("rsa")) {
            return rsaDecrypt(data);
        } else if (algName.toLowerCase().contains("sm4")) {
            return sm4Decrypt(data);
        } else {
            return aesDecrypt(data);
        }
    }

    public String encodeMd5(String s) {
        char[] hexDigits = {'0', '1', '2', '3', '4',
                '5', '6', '7', '8', '9',
                'a', 'b', 'c', 'd', 'e', 'f'};
        try {
            byte[] btInput = s.getBytes();
            //获得MD5摘要算法的 MessageDigest 对象
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            //使用指定的字节更新摘要
            mdInst.update(btInput);
            //获得密文
            byte[] md = mdInst.digest();
            //把密文转换成十六进制的字符串形式
            int j = md.length;
            char[] str = new char[j * 2];
            int k = 0;
            for (byte byte0 : md) {
                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
                str[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(str);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private static ByteArrayOutputStream RsaAlgorithm(String data, boolean isEncrypt) {
        try {
            int MAX_BLOCK;
            byte[] byteData;
            if (isEncrypt) {
                PublicKey publicKey = (PublicKey) cacheModule.get("rsaPublicKey");
                cipher.init(Cipher.ENCRYPT_MODE, publicKey);
                MAX_BLOCK = MAX_ENCRYPT_BLOCK;
                byteData = EncodingTool.encode(data.getBytes()).getBytes();
            } else {
                PrivateKey privateKey = (PrivateKey) cacheModule.get("rsaPrivateKey");
                cipher.init(Cipher.DECRYPT_MODE, privateKey);
                MAX_BLOCK = MAX_DECRYPT_BLOCK;
                byteData = EncodingTool.hexStringToBytes(data);
            }
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int inputLen = byteData.length;
            int offSet = 0;
            byte[] cache;
            int i = 0;
            // 对数据分段加解密
            while (inputLen - offSet > 0) {
                if (inputLen - offSet > MAX_BLOCK) {
                    cache = cipher.doFinal(byteData, offSet, MAX_BLOCK);
                } else {
                    cache = cipher.doFinal(byteData, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_BLOCK;
            }
            return out;
        } catch (InvalidKeyException e) {
            throw new RuntimeException("非法的Key值" + e.getMessage());
        } catch (IllegalBlockSizeException e) {
            throw new RuntimeException("数据参数大小错误" + e.getMessage());
        } catch (BadPaddingException e) {
            throw new RuntimeException("错误的填充模式" + e.getMessage());
        }
    }

    private String rsaEncrypt(String data) {
        ByteArrayOutputStream out = RsaAlgorithm(data, true);
        return EncodingTool.getHexString(EncodingTool.encode(out.toByteArray()), true);
    }

    private String rsaDecrypt(String data) {
        ByteArrayOutputStream out = RsaAlgorithm(data, false);
        String de_input = new String(
                Objects.requireNonNull(out.toByteArray()),
                StandardCharsets.UTF_8);
        return new String(EncodingTool.decode(de_input), StandardCharsets.UTF_8);
    }

    private String aesEncrypt(String data) {
        SecretKey secretKey = (SecretKey) cacheModule.get("aesKey");
        IvParameterSpec iv = (IvParameterSpec) cacheModule.get("aesIv");
        try {
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
            int blockSize = cipher.getBlockSize();
            byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
            int plaintextLength = dataBytes.length;
            if (plaintextLength % blockSize != 0) {
                plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
            }
            byte[] plaintext = new byte[plaintextLength];
            System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
            byte[] encrypted = cipher.doFinal(plaintext);
            return new Base64().encodeToString(encrypted);
        } catch (InvalidKeyException e) {
            throw new RuntimeException("非法的Key值" + e.getMessage());
        } catch (InvalidAlgorithmParameterException e) {
            throw new RuntimeException("非法的加密类型值" + e.getMessage());
        } catch (IllegalBlockSizeException e) {
            throw new RuntimeException("数据参数大小错误" + e.getMessage());
        } catch (BadPaddingException e) {
            throw new RuntimeException("错误的填充模式" + e.getMessage());
        }
    }

    private String aesDecrypt(String data) {
        SecretKey secretKey = (SecretKey) cacheModule.get("aesKey");
        IvParameterSpec iv = (IvParameterSpec) cacheModule.get("aesIv");
        try {
            cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
            byte[] original = cipher.doFinal(new Base64().decode(data));
            return new String(original, StandardCharsets.UTF_8).trim();
        } catch (InvalidKeyException e) {
            throw new RuntimeException("非法的Key值" + e.getMessage());
        } catch (InvalidAlgorithmParameterException e) {
            throw new RuntimeException("非法的加密类型值" + e.getMessage());
        } catch (IllegalBlockSizeException e) {
            throw new RuntimeException("数据参数大小错误" + e.getMessage());
        } catch (BadPaddingException e) {
            throw new RuntimeException("错误的填充模式" + e.getMessage());
        }
    }

    private long GET_ULONG_BE(byte[] b, int i) {
        long n = (long) (b[i] & 0xff) << 24 | (long) ((b[i + 1] & 0xff) << 16) | (long) ((b[i + 2] & 0xff) << 8) | (long) (b[i + 3] & 0xff) & 0xffffffffL;
        return n;
    }

    private void sm4_setkey(long[] SK, byte[] key, boolean isEncrypt) {
        long[] MK = new long[4];
        long[] k = new long[36];
        int i = 0;
        MK[0] = GET_ULONG_BE(key, 0);
        MK[1] = GET_ULONG_BE(key, 4);
        MK[2] = GET_ULONG_BE(key, 8);
        MK[3] = GET_ULONG_BE(key, 12);
        k[0] = MK[0] ^ (long) FK[0];
        k[1] = MK[1] ^ (long) FK[1];
        k[2] = MK[2] ^ (long) FK[2];
        k[3] = MK[3] ^ (long) FK[3];
        for (; i < 32; i++) {
            k[(i + 4)] = (k[i] ^ sm4CalciRK(k[(i + 1)] ^ k[(i + 2)] ^ k[(i + 3)] ^ (long) CK[i]));
            SK[i] = k[(i + 4)];
        }
        if (!isEncrypt) {
            for (i = 0; i < 16; i++) {
                SWAP(SK, i);
            }
        }
    }

    private void SWAP(long[] sk, int i) {
        long t = sk[i];
        sk[i] = sk[(31 - i)];
        sk[(31 - i)] = t;
    }

    private void PUT_ULONG_BE(long n, byte[] b, int i) {
        b[i] = (byte) (int) (0xFF & n >> 24);
        b[i + 1] = (byte) (int) (0xFF & n >> 16);
        b[i + 2] = (byte) (int) (0xFF & n >> 8);
        b[i + 3] = (byte) (int) (0xFF & n);
    }

    private byte sm4Sbox(byte inch) {
        int i = inch & 0xFF;
        byte retVal = SboxTable[i];
        return retVal;
    }

    private long sm4CalciRK(long ka) {
        long bb = 0L;
        long rk = 0L;
        byte[] a = new byte[4];
        byte[] b = new byte[4];
        PUT_ULONG_BE(ka, a, 0);
        b[0] = sm4Sbox(a[0]);
        b[1] = sm4Sbox(a[1]);
        b[2] = sm4Sbox(a[2]);
        b[3] = sm4Sbox(a[3]);
        bb = GET_ULONG_BE(b, 0);
        rk = bb ^ ROTL(bb, 13) ^ ROTL(bb, 23);
        return rk;
    }

    private long SHL(long x, int n) {
        return (x & 0xFFFFFFFF) << n;
    }

    private long ROTL(long x, int n) {
        return SHL(x, n) | x >> (32 - n);
    }

    private SM4Context initSM4Context(boolean isEncrypt) {
        int mode = isEncrypt ? SM4_ENCRYPT : SM4_DECRYPT;
        return new SM4Context(mode, true);
    }

    private byte[] padding(byte[] input, int mode) {
        if (input == null) {
            return null;
        }
        byte[] ret;
        if (mode == SM4_ENCRYPT) {
            int p = 16 - input.length % 16;
            ret = new byte[input.length + p];
            System.arraycopy(input, 0, ret, 0, input.length);
            for (int i = 0; i < p; i++) {
                ret[input.length + i] = (byte) p;
            }
        } else {
            int p = input[input.length - 1];
            ret = new byte[input.length - p];
            System.arraycopy(input, 0, ret, 0, input.length - p);
        }
        return ret;
    }

    private byte[] sm4_ecb(String data, boolean isEncrypt) {
        SecretKey secretKey = (SecretKey) cacheModule.get("aesKey");
//        byte[] byteKey = secretKey.getEncoded();
        byte[] byteKey = "1234567891023456".getBytes(StandardCharsets.UTF_8);
        SM4Context sm4 = initSM4Context(isEncrypt);
        sm4_setkey(sm4.sk, byteKey, isEncrypt);// 生成Key
        byte[] byteData;
        if (isEncrypt) {
            byteData = data.getBytes(StandardCharsets.UTF_8);
        } else {
            byteData = java.util.Base64.getDecoder().decode(data);
        }
        ByteArrayInputStream bins;
        ByteArrayOutputStream bous;
        if ((sm4.isPadding) && (sm4.mode == SM4_ENCRYPT)) {
            byteData = padding(byteData, SM4_ENCRYPT);
        }
        try {
            int length = byteData.length;
            bins = new ByteArrayInputStream(byteData);
            bous = new ByteArrayOutputStream();
            for (; length > 0; length -= 16) {
                byte[] in = new byte[16];
                byte[] out = new byte[16];
                bins.read(in);
                EncodingTool.sm4_one_round(sm4.sk, in, out);
                bous.write(out);
            }
            byte[] output = bous.toByteArray();
            if (sm4.isPadding && sm4.mode == SM4_DECRYPT) {
                output = padding(output, SM4_DECRYPT);
            }
            bins.close();
            bous.close();
            return output;
        } catch (IOException e) {
            throw new RuntimeException("SM4流问题" + e.getMessage());
        }
    }

    private byte[] sm4_cbc(String data, boolean isEncrypt) {
        SecretKey secretKey = (SecretKey) cacheModule.get("aesKey");
//        byte[] byteKey = secretKey.getEncoded();
        byte[] byteKey = "1234567891023456".getBytes(StandardCharsets.UTF_8);
        IvParameterSpec iv = (IvParameterSpec) cacheModule.get("aesIv");
//        byte[] byteIv = iv.getIV();
        byte[] byteIv = "1234567891023456".getBytes(StandardCharsets.UTF_8);
        SM4Context sm4 = initSM4Context(isEncrypt);
        sm4_setkey(sm4.sk, byteKey, isEncrypt);// 生成Key
        byte[] byteData;
        if (isEncrypt) {
            byteData = data.getBytes(StandardCharsets.UTF_8);
        } else {
            byteData = java.util.Base64.getDecoder().decode(data);
        }

        if (sm4.isPadding && sm4.mode == SM4_ENCRYPT) {
            byteData = padding(byteData, SM4_ENCRYPT);
        }
        try {


            int i = 0;
            int length = byteData.length;
            ByteArrayInputStream bins = new ByteArrayInputStream(byteData);
            ByteArrayOutputStream bous = new ByteArrayOutputStream();
            if (sm4.mode == SM4_ENCRYPT) {
                for (; length > 0; length -= 16) {
                    byte[] in = new byte[16];
                    byte[] out = new byte[16];
                    byte[] out1 = new byte[16];
                    bins.read(in);
                    for (i = 0; i < 16; i++) {
                        out[i] = ((byte) (in[i] ^ byteIv[i]));
                    }
                    EncodingTool.sm4_one_round(sm4.sk, out, out1);
                    System.arraycopy(out1, 0, byteIv, 0, 16);
                    bous.write(out1);
                }
            } else {
                byte[] temp = new byte[16];
                for (; length > 0; length -= 16) {
                    byte[] in = new byte[16];
                    byte[] out = new byte[16];
                    byte[] out1 = new byte[16];

                    bins.read(in);
                    System.arraycopy(in, 0, temp, 0, 16);
                    EncodingTool.sm4_one_round(sm4.sk, in, out);
                    for (i = 0; i < 16; i++) {
                        out1[i] = ((byte) (out[i] ^ byteIv[i]));
                    }
                    System.arraycopy(temp, 0, byteIv, 0, 16);
                    bous.write(out1);
                }
            }
            byte[] output = bous.toByteArray();
            if (sm4.isPadding && sm4.mode == SM4_DECRYPT) {
                output = padding(output, SM4_DECRYPT);
            }
            bins.close();
            bous.close();
            return output;
        } catch (IOException e) {
            throw new RuntimeException("SM4_CBC流问题" + e.getMessage());
        }
    }

    private String sm4Encrypt(String data) {
        byte[] byteData;
        if (algName.toLowerCase().contains("cbc")) {
            byteData = sm4_cbc(data, true);
        } else {
            byteData = sm4_ecb(data, true);
        }
        return EncodingTool.sm4Encode(byteData);
    }

    private String sm4Decrypt(String data) {
        byte[] byteData;
        if (algName.toLowerCase().contains("cbc")) {
            byteData = sm4_cbc(data, false);
        } else {
            byteData = sm4_ecb(data, false);
        }
        return new String(byteData, StandardCharsets.UTF_8);
    }

    public static void main(String[] args) {
        EncryptAndDecryptTool ecb = EncryptAndDecryptTool.getInstance(ALGORITHM_TYPE.SM4_ECB);
        String e1 = ecb.encrypt("Sanders");
        String de1 = ecb.decrypt(e1);
        System.out.println(e1);
        System.out.println(de1);
        System.out.println("______________");
        EncryptAndDecryptTool cbc = EncryptAndDecryptTool.getInstance(ALGORITHM_TYPE.SM4_CBC);
        String cbc_e1 = cbc.encrypt("Sanders");
        String cbc_de1 = cbc.decrypt(cbc_e1);
        System.out.println(cbc_e1);
        System.out.println(cbc_de1);
    }
}

class EncodingTool {

    static private final int BASELENGTH = 128;
    static private final int LOOKUPLENGTH = 64;
    static private final int TWENTYFOURBITGROUP = 24;
    static private final int EIGHTBIT = 8;
    static private final int SIXTEENBIT = 16;
    static private final int FOURBYTE = 4;
    static private final int SIGN = -128;
    static private final char PAD = '=';
    static private final boolean fDebug = false;
    static final private byte[] base64Alphabet = new byte[BASELENGTH];
    static final private char[] lookUpBase64Alphabet = new char[LOOKUPLENGTH];
    public static final byte[] SboxTable = {(byte) 0xd6, (byte) 0x90, (byte) 0xe9, (byte) 0xfe,
            (byte) 0xcc, (byte) 0xe1, 0x3d, (byte) 0xb7, 0x16, (byte) 0xb6,
            0x14, (byte) 0xc2, 0x28, (byte) 0xfb, 0x2c, 0x05, 0x2b, 0x67,
            (byte) 0x9a, 0x76, 0x2a, (byte) 0xbe, 0x04, (byte) 0xc3,
            (byte) 0xaa, 0x44, 0x13, 0x26, 0x49, (byte) 0x86, 0x06,
            (byte) 0x99, (byte) 0x9c, 0x42, 0x50, (byte) 0xf4, (byte) 0x91,
            (byte) 0xef, (byte) 0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43,
            (byte) 0xed, (byte) 0xcf, (byte) 0xac, 0x62, (byte) 0xe4,
            (byte) 0xb3, 0x1c, (byte) 0xa9, (byte) 0xc9, 0x08, (byte) 0xe8,
            (byte) 0x95, (byte) 0x80, (byte) 0xdf, (byte) 0x94, (byte) 0xfa,
            0x75, (byte) 0x8f, 0x3f, (byte) 0xa6, 0x47, 0x07, (byte) 0xa7,
            (byte) 0xfc, (byte) 0xf3, 0x73, 0x17, (byte) 0xba, (byte) 0x83,
            0x59, 0x3c, 0x19, (byte) 0xe6, (byte) 0x85, 0x4f, (byte) 0xa8,
            0x68, 0x6b, (byte) 0x81, (byte) 0xb2, 0x71, 0x64, (byte) 0xda,
            (byte) 0x8b, (byte) 0xf8, (byte) 0xeb, 0x0f, 0x4b, 0x70, 0x56,
            (byte) 0x9d, 0x35, 0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, (byte) 0xd1,
            (byte) 0xa2, 0x25, 0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, (byte) 0x87,
            (byte) 0xd4, 0x00, 0x46, 0x57, (byte) 0x9f, (byte) 0xd3, 0x27,
            0x52, 0x4c, 0x36, 0x02, (byte) 0xe7, (byte) 0xa0, (byte) 0xc4,
            (byte) 0xc8, (byte) 0x9e, (byte) 0xea, (byte) 0xbf, (byte) 0x8a,
            (byte) 0xd2, 0x40, (byte) 0xc7, 0x38, (byte) 0xb5, (byte) 0xa3,
            (byte) 0xf7, (byte) 0xf2, (byte) 0xce, (byte) 0xf9, 0x61, 0x15,
            (byte) 0xa1, (byte) 0xe0, (byte) 0xae, 0x5d, (byte) 0xa4,
            (byte) 0x9b, 0x34, 0x1a, 0x55, (byte) 0xad, (byte) 0x93, 0x32,
            0x30, (byte) 0xf5, (byte) 0x8c, (byte) 0xb1, (byte) 0xe3, 0x1d,
            (byte) 0xf6, (byte) 0xe2, 0x2e, (byte) 0x82, 0x66, (byte) 0xca,
            0x60, (byte) 0xc0, 0x29, 0x23, (byte) 0xab, 0x0d, 0x53, 0x4e, 0x6f,
            (byte) 0xd5, (byte) 0xdb, 0x37, 0x45, (byte) 0xde, (byte) 0xfd,
            (byte) 0x8e, 0x2f, 0x03, (byte) 0xff, 0x6a, 0x72, 0x6d, 0x6c, 0x5b,
            0x51, (byte) 0x8d, 0x1b, (byte) 0xaf, (byte) 0x92, (byte) 0xbb,
            (byte) 0xdd, (byte) 0xbc, 0x7f, 0x11, (byte) 0xd9, 0x5c, 0x41,
            0x1f, 0x10, 0x5a, (byte) 0xd8, 0x0a, (byte) 0xc1, 0x31,
            (byte) 0x88, (byte) 0xa5, (byte) 0xcd, 0x7b, (byte) 0xbd, 0x2d,
            0x74, (byte) 0xd0, 0x12, (byte) 0xb8, (byte) 0xe5, (byte) 0xb4,
            (byte) 0xb0, (byte) 0x89, 0x69, (byte) 0x97, 0x4a, 0x0c,
            (byte) 0x96, 0x77, 0x7e, 0x65, (byte) 0xb9, (byte) 0xf1, 0x09,
            (byte) 0xc5, 0x6e, (byte) 0xc6, (byte) 0x84, 0x18, (byte) 0xf0,
            0x7d, (byte) 0xec, 0x3a, (byte) 0xdc, 0x4d, 0x20, 0x79,
            (byte) 0xee, 0x5f, 0x3e, (byte) 0xd7, (byte) 0xcb, 0x39, 0x48};


    static {
        for (int i = 0; i < BASELENGTH; ++i) {
            base64Alphabet[i] = -1;
        }
        for (int i = 'Z'; i >= 'A'; i--) {
            base64Alphabet[i] = (byte) (i - 'A');
        }
        for (int i = 'z'; i >= 'a'; i--) {
            base64Alphabet[i] = (byte) (i - 'a' + 26);
        }

        for (int i = '9'; i >= '0'; i--) {
            base64Alphabet[i] = (byte) (i - '0' + 52);
        }

        base64Alphabet['+'] = 62;
        base64Alphabet['/'] = 63;

        for (int i = 0; i <= 25; i++) {
            lookUpBase64Alphabet[i] = (char) ('A' + i);
        }

        for (int i = 26, j = 0; i <= 51; i++, j++) {
            lookUpBase64Alphabet[i] = (char) ('a' + j);
        }

        for (int i = 52, j = 0; i <= 61; i++, j++) {
            lookUpBase64Alphabet[i] = (char) ('0' + j);
        }
        lookUpBase64Alphabet[62] = '+';
        lookUpBase64Alphabet[63] = '/';

    }

    public static String sm4Encode(byte[] encrypted) {
        String cipherText = java.util.Base64.getEncoder().encodeToString(encrypted);
        if (cipherText != null && cipherText.trim().length() > 0) {
            Pattern p = compile("\\s*|\t|\r|\n");
            Matcher m = p.matcher(cipherText);
            cipherText = m.replaceAll("");
        }
        return cipherText;
    }


    public static void sm4_one_round(long[] sk, byte[] input, byte[] output) {
        int i = 0;
        long[] ulbuf = new long[36];
        ulbuf[0] = GET_ULONG_BE(input, 0);
        ulbuf[1] = GET_ULONG_BE(input, 4);
        ulbuf[2] = GET_ULONG_BE(input, 8);
        ulbuf[3] = GET_ULONG_BE(input, 12);
        while (i < 32) {
            ulbuf[(i + 4)] = sm4F(ulbuf[i], ulbuf[(i + 1)], ulbuf[(i + 2)], ulbuf[(i + 3)], sk[i]);
            i++;
        }
        PUT_ULONG_BE(ulbuf[35], output, 0);
        PUT_ULONG_BE(ulbuf[34], output, 4);
        PUT_ULONG_BE(ulbuf[33], output, 8);
        PUT_ULONG_BE(ulbuf[32], output, 12);
    }

    /**
     * 根据字节数组获得值(十六进制数字)
     */
    public static String getHexString(String str, boolean upperCase) {
        byte[] bytes = decode(str);
        StringBuilder ret = new StringBuilder();
        for (byte aByte : bytes) {
            ret.append(Integer.toString((aByte & 0xff) + 0x100, 16).substring(1));
        }
        return upperCase ? ret.toString().toUpperCase() : ret.toString();
    }

    public static byte charToByte(char c) {
        return (byte) "0123456789ABCDEF".indexOf(c);
    }

    public static byte[] hexStringToBytes(String hexString) {
        if (hexString == null || hexString.equals("")) {
            return null;
        }
        hexString = hexString.toUpperCase();
        int length = hexString.length() / 2;
        char[] hexChars = hexString.toCharArray();
        byte[] d = new byte[length];
        for (int i = 0; i < length; i++) {
            int pos = i * 2;
            d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
        }
        return d;
    }

    private static long sm4Lt(long ka) {
        long bb = 0L;
        long c = 0L;
        byte[] a = new byte[4];
        byte[] b = new byte[4];
        PUT_ULONG_BE(ka, a, 0);
        b[0] = sm4Sbox(a[0]);
        b[1] = sm4Sbox(a[1]);
        b[2] = sm4Sbox(a[2]);
        b[3] = sm4Sbox(a[3]);
        bb = GET_ULONG_BE(b, 0);
        c = bb ^ ROTL(bb, 2) ^ ROTL(bb, 10) ^ ROTL(bb, 18) ^ ROTL(bb, 24);
        return c;
    }

    private static long SHL(long x, int n) {
        return (x & 0xFFFFFFFF) << n;
    }

    private static long ROTL(long x, int n) {
        return SHL(x, n) | x >> (32 - n);
    }

    private static byte sm4Sbox(byte inch) {
        int i = inch & 0xFF;
        return SboxTable[i];
    }

    private static long sm4F(long x0, long x1, long x2, long x3, long rk) {
        return x0 ^ sm4Lt(x1 ^ x2 ^ x3 ^ rk);
    }

    private static long GET_ULONG_BE(byte[] b, int i) {
        long n = (long) (b[i] & 0xff) << 24 | (long) ((b[i + 1] & 0xff) << 16) | (long) ((b[i + 2] & 0xff) << 8) | (long) (b[i + 3] & 0xff) & 0xffffffffL;
        return n;
    }

    private static void PUT_ULONG_BE(long n, byte[] b, int i) {
        b[i] = (byte) (int) (0xFF & n >> 24);
        b[i + 1] = (byte) (int) (0xFF & n >> 16);
        b[i + 2] = (byte) (int) (0xFF & n >> 8);
        b[i + 3] = (byte) (int) (0xFF & n);
    }

    private static int removeWhiteSpace(char[] data) {
        if (data == null) {
            return 0;
        }

        // count characters that's not whitespace
        int newSize = 0;
        int len = data.length;
        for (int i = 0; i < len; i++) {
            if (!isWhiteSpace(data[i])) {
                data[newSize++] = data[i];
            }
        }
        return newSize;
    }

    private static boolean isData(char octet) {
        return (octet < BASELENGTH && base64Alphabet[octet] != -1);
    }

    private static boolean isPad(char octet) {
        return (octet == PAD);
    }

    private static boolean isWhiteSpace(char octect) {
        return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9);
    }

    public static String encode(byte[] binaryData) {
        if (binaryData == null) {
            return null;
        }
        int lengthDataBits = binaryData.length * EIGHTBIT;
        if (lengthDataBits == 0) {
            return "";
        }
        int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP;
        int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP;
        int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1 : numberTriplets;
        char[] encodedData = null;
        encodedData = new char[numberQuartet * 4];
        byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0;
        int encodedIndex = 0;
        int dataIndex = 0;
        for (int i = 0; i < numberTriplets; i++) {
            b1 = binaryData[dataIndex++];
            b2 = binaryData[dataIndex++];
            b3 = binaryData[dataIndex++];
            l = (byte) (b2 & 0x0f);
            k = (byte) (b1 & 0x03);
            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
            byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
            byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc);
            if (fDebug) {
                System.out.println("val2 = " + val2);
                System.out.println("k4   = " + (k << 4));
                System.out.println("vak  = " + (val2 | (k << 4)));
            }
            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f];
        }
        if (fewerThan24bits == EIGHTBIT) {
            b1 = binaryData[dataIndex];
            k = (byte) (b1 & 0x03);
            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[k << 4];
            encodedData[encodedIndex++] = PAD;
            encodedData[encodedIndex++] = PAD;
        } else if (fewerThan24bits == SIXTEENBIT) {
            b1 = binaryData[dataIndex];
            b2 = binaryData[dataIndex + 1];
            l = (byte) (b2 & 0x0f);
            k = (byte) (b1 & 0x03);
            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
            byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[l << 2];
            encodedData[encodedIndex++] = PAD;
        }
        return new String(encodedData);
    }

    public static byte[] decode(String encoded) {
        if (encoded == null) {
            return null;
        }
        char[] base64Data = encoded.toCharArray();
        // remove white spaces
        int len = removeWhiteSpace(base64Data);

        if (len % FOURBYTE != 0) {
            return null;//should be divisible by four
        }

        int numberQuadruple = (len / FOURBYTE);

        if (numberQuadruple == 0) {
            return new byte[0];
        }

        byte decodedData[] = null;
        byte b1 = 0, b2 = 0, b3 = 0, b4 = 0;
        char d1 = 0, d2 = 0, d3 = 0, d4 = 0;

        int i = 0;
        int encodedIndex = 0;
        int dataIndex = 0;
        decodedData = new byte[(numberQuadruple) * 3];

        for (; i < numberQuadruple - 1; i++) {

            if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))
                    || !isData((d3 = base64Data[dataIndex++]))
                    || !isData((d4 = base64Data[dataIndex++]))) {
                return null;
            }//if found "no data" just return null

            b1 = base64Alphabet[d1];
            b2 = base64Alphabet[d2];
            b3 = base64Alphabet[d3];
            b4 = base64Alphabet[d4];

            decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
            decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
            decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);
        }

        if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))) {
            return null;//if found "no data" just return null
        }

        b1 = base64Alphabet[d1];
        b2 = base64Alphabet[d2];

        d3 = base64Data[dataIndex++];
        d4 = base64Data[dataIndex++];
        if (!isData((d3)) || !isData((d4))) {//Check if they are PAD characters
            if (isPad(d3) && isPad(d4)) {
                if ((b2 & 0xf) != 0)//last 4 bits should be zero
                {
                    return null;
                }
                byte[] tmp = new byte[i * 3 + 1];
                System.arraycopy(decodedData, 0, tmp, 0, i * 3);
                tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
                return tmp;
            } else if (!isPad(d3) && isPad(d4)) {
                b3 = base64Alphabet[d3];
                if ((b3 & 0x3) != 0)//last 2 bits should be zero
                {
                    return null;
                }
                byte[] tmp = new byte[i * 3 + 2];
                System.arraycopy(decodedData, 0, tmp, 0, i * 3);
                tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
                tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
                return tmp;
            } else {
                return null;
            }
        } else { //No PAD e.g 3cQl
            b3 = base64Alphabet[d3];
            b4 = base64Alphabet[d4];
            decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
            decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
            decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);

        }

        return decodedData;
    }
}

class SM4Context {
    public int mode;

    public long[] sk;

    public boolean isPadding;

    public SM4Context() {
        this.mode = 1;
        this.isPadding = true;
        this.sk = new long[32];
    }

    public SM4Context(int mode, boolean isPadding) {
        this.mode = mode;
        this.isPadding = isPadding;
        this.sk = new long[32];
    }
}

enum ALGORITHM_TYPE {
    AES_CBC_NoPadding,
    AES_CBC_PKCS5Padding,
    AES_ECB_NoPadding,
    AES_ECB_PKCS5Padding,
    RSA_ECB_PKCS1Padding,
    SM4_ECB,
    SM4_CBC
}
